#include <Arduino.h>
#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <wire.h>
#include <Adafruit_NeoMatrix.h>   //点亮LED矩阵需要的库
#include "my_font.h"              //引用文字库头文件
#include <DHT.h>                  //温湿度传感器驱动库
#include "Bounce2.h"              //按键库
#include "weather.h"              //定义Weather类，用于解析Json数据并返回天气数据
#include "anim.h"                 //包含部分动画以及图标
#include "musicfft.h"             //音乐频谱库
#include "Ds1302.h"               //DS1302的驱动库
#include "wifi_network.h"         //WIFI配网
#include "VibrationMotor.h"
#include "voiceSu03t.h"

//WIFI相关定义 （用于获取网络时间进行校准 和 获取天气数据）
#define WIFI_SSID "lckfb"           //开发板要连接的WIFI名称
#define WIFI_PASSWORD "12345678"    //开发板要连接的WIFI密码
#define WIFI_RETRY_COUNT  20        //WIFI连接重试数
#define GET_TIME_TIMEOUT  10        //WIFI获取网络时间的重试数
#define NTP_SERVER "pool.ntp.org"   // 定义NTP服务器
#define GMT_OFFSET  8               // 定义时区（以小时为单位，例如：+1或-3）
#define DAY_LIGHT_OFFSET  0 // 定义夏令时（Daylight Saving Time，DST）的偏移量（通常为0或3600秒）
//WIFI配网功能
Network network("ESP32S3-PixelClock","ESP32S3-PixelClock");//AP热点名称,设置设备名
//WIFI获取NTP网络时间功能
WiFiUDP ntpUDP;     // 创建UDP实例
// 创建NTP客户端实例
NTPClient timeClient(ntpUDP, NTP_SERVER, GMT_OFFSET * 3600, DAY_LIGHT_OFFSET * 3600);
struct tm tm_info;  //NTP设定时间值

//计时模块DS1302相关定义
#define RST 10              //RST引脚
#define DATA 3              //DATA引脚
#define CLK 2               //CLK引脚
Ds1302 rtc(RST, CLK, DATA); //创建DS1302这个对象

//DHT11温湿度传感器相关定义
#define DHTPIN 4            //传感器引脚连接4
#define DHTTYPE DHT11       //传感器支持DHT11, DHT12, DHT21, DHT22, AM2301
#define TIMEDHT 1000        //每秒钟更新一次温湿度
uint32_t timerDHT = TIMEDHT; 
float humidity, temperature;
DHT dht(DHTPIN, DHTTYPE);   //初始化温湿度传感器

//彩灯像素矩阵定义
#define kMatrixWidth   32             //宽度
#define kMatrixHeight  8              //高度
#define BRIGHTNESS     150            //默认亮度 0-255
#define BRIGHTNESS_INTERVAL 30        //亮度调节间隔
#define LED_PIN        8              //像素矩阵引脚
uint8_t brightness = BRIGHTNESS;      //亮度
bool change = false;                  //亮度调节标志位,为True时才能改变亮度
bool dir = 0;
Adafruit_NeoMatrix *matrix;     //LED矩阵类指针

//和风天气预报
String UserKey = "d89d4706adcc4fb89d6f166cb420ee39";       //密钥
String Location = "101280601";      //地点标识：深圳
int weatherid;                      //根据和风天气API返回的天气ID显示对应天气
bool is_day = 1;                    //白天为1，夜晚为0
bool is_today = 1;                  //今天为1，明天为0
bool getweather = false;            //为True时才能获取天气
Weather weather(UserKey, Location); //创建天气对象

//时间相关定义
uint8_t hour, minute, second, week, month,year, day;
bool need_set_time = false; //是否需要重新设定时间，用于NTP同步成功时触发
int anim_index = 0;         //用于动画显示

//界面内容显示
enum displaytype{timedisplay, weatherdisplay, roomparameter, raindisplay, music_fft}; //用于显示内容选择
displaytype display = timedisplay; //初始化显示时间界面
uint8_t rain_count = 0;

//按键引脚定义
#define BUTTONPIN 0       //按键引脚
Bounce button = Bounce(); //初始化按键对象

//亮度自动控制功能 （光敏电阻）
#define LIGHTPIN 5          //光敏电阻引脚
#define LIGHT_GET_MAX 200   //光照最亮时读取光敏电阻得到的值
#define LIGHT_GET_MIN 4095  //光照最暗时读取光敏电阻得到的值
#define LIGHT_CON_MAX 255   //控制矩阵最高显示的亮度
#define LIGHT_CON_MIN 10    //控制矩阵最低显示的亮度

// 定义振动电机连接的引脚
const int motorPin = 11; 
VibrationMotor vibrationMotor(motorPin); // 创建振动电机对象

//语音识别模块定义
#define VOICE_PORT 1            //语音识别模块使用的串口
#define VOICE_TX_PIN 12         //语音识别模块使用的串口TX引脚
#define VOICE_RX_PIN 13         //语音识别模块使用的串口RX引脚
#define VOICE_TX_BAUDRATE 9600  //语音识别模块使用的串口波特率
//创建语音识别对象
VOICESU03T Su03t(VOICE_PORT, VOICE_TX_PIN, VOICE_RX_PIN, VOICE_TX_BAUDRATE); 
char su03t_rx_buff[20];
char su03t_rx_len;
/* 语音表
帧头    数据    帧尾    说明
EF      00      FE      被语音唤醒，唤醒词：小豆小豆|小豆同学|你好小豆
EF      01      FE      识别到语音：当前温湿度|现在温湿度|现在的温湿度|温湿度是多少
EF      02      FE      识别到语音：当前时间|现在时间|现在的时间|现在是什么时候了|现在多少点了|现在时间是多少|报时  
EF      03      FE      识别到语音：显示时间|切换时间界面|切换时间|显示时钟|切换时钟界面|切换时钟
EF      04      FE      识别到语音：显示天气|切换天气|切换天气界面|显示天气界面 
EF      05      FE      识别到语音：显示温湿度|切换温湿度|切换温湿度界面|显示温湿度界面
EF      06      FE      识别到语音：显示数字雨|切换数字雨|切换数字雨界面|显示数字雨界面
EF      07      FE      识别到语音：显示频谱灯|显示音律灯|切换频谱灯|切换音律灯|切换频谱灯界面|切换音频灯界面|显示频谱灯界面|显示音律灯界面
EF      08      FE      识别到语音：显示切换|切屏|界面切换|切换界面|显示下一个
 */



void InitDS1302() //DS1302初始化
{
  //创建基于DS1302库下的时间参数
  Ds1302::DateTime dt = {
      .year = tm_info.tm_year,
      .month = tm_info.tm_mon,//Ds1302::MONTH_JUL,
      .day = tm_info.tm_mday,
      .hour = tm_info.tm_hour,
      .minute = tm_info.tm_min,
      .second = tm_info.tm_sec,
      .dow = tm_info.tm_wday//Ds1302::DOW_SAT
  };
  //将设置的时间写入DS1302模块
  rtc.setDateTime(&dt);
}  
void readRtc()//读取DS1302 RTC寄存器，获取准确时间
{
  Ds1302::DateTime tim;

  //获取当前时间并且存储在tim中
  rtc.getDateTime(&tim);

  second = tim.second;
  minute = tim.minute;
  hour = tim.hour;
  week = tim.dow;
  day = tim.day;
  month = tim.month;
  year = tim.year+2000;
}

void readDHT() //温湿度传感器数值读取
{
  if ((millis() - timerDHT) > TIMEDHT) {
    timerDHT = millis();

    humidity = dht.readHumidity();

    temperature = dht.readTemperature();

    if (isnan(humidity) || isnan(temperature)) {
      Serial.println("Failed to read from DHT sensor!");
      return;
    }
  }
}


void InitLED_Matrix()//像素矩阵初始化
{
  //设置像素矩阵的方向以及排列方式
  matrix = new Adafruit_NeoMatrix(32, 8, LED_PIN, NEO_MATRIX_TOP + NEO_MATRIX_LEFT+
    NEO_MATRIX_COLUMNS +  NEO_MATRIX_ZIGZAG,
    NEO_GRB + NEO_KHZ800);
  matrix->setTextWrap(false); //设置文字是否自动换行
  matrix->setFont(&Picopixel);
  matrix->clear();
  matrix->setBrightness(BRIGHTNESS);
}



//获取NTP时间
//失败返回false 成功返回true
bool getTimeNTP(void)
{
  char output[100]={0};

  // 启动NTP客户端
  timeClient.begin();
  // 设置时间同步间隔（以毫秒为单位）
  timeClient.setUpdateInterval(1000); // 1秒更新一次
  // 更新NTP时间
  timeClient.update();
  
  if (timeClient.getEpochTime() > 0) 
  {
    // 获取成功
    unsigned long epochTime = timeClient.getEpochTime();
    struct tm *ptm = gmtime ((time_t *)&epochTime); 

    //如果返回的时间是1970，说明错误了
    if( ptm->tm_year == 70 ) 
    {
      Serial.print("Failed to obtain the time. Try again\r\n");
      return false;
    }

    //NTP（网络时间协议）返回的时间通常包含自1900年1月1日以来的秒数。年部分是从1900年开始计算的。
    tm_info.tm_year = ptm->tm_year + 1900;
    tm_info.tm_mon = ptm->tm_mon + 1;
    tm_info.tm_mday = ptm->tm_mday;
    tm_info.tm_hour = ptm->tm_hour;
    tm_info.tm_min = ptm->tm_min;
    tm_info.tm_sec = ptm->tm_sec;
    tm_info.tm_wday = ptm->tm_wday;

    sprintf( output,
             "NTP time = %d-%d-%d %d:%d:%d %d\r\n", 
             tm_info.tm_year, 
             tm_info.tm_mon, 
             tm_info.tm_mday, 
             tm_info.tm_hour, 
             tm_info.tm_min, 
             tm_info.tm_sec, 
             tm_info.tm_wday );        
    Serial.println(output);

    need_set_time = true;//开启DS1302校准时间
  } 
  else 
  {
    // 获取失败，可以在这里进行重试或错误处理
    Serial.println("Failed to get NTP time\r\n");
    return false;
  }
  return true;
}

void showFFT()//显示FFT音律灯
{
    //进行采样
    getSamples();
    //更新频谱柱
    displayUpdate(matrix, 2);
}

//亮度自动调节
//最亮时最小200
//最暗时最高4095
// 3V3->1.8K->光敏电阻->GND
// GPIO5接在1.8K电阻和光敏电阻中间
//返回当前设置的亮度
int lightAutoControl(Adafruit_NeoMatrix *matrix)
{
  int lightValue = 0;
  int adjustedValue = 0;

  lightValue = analogRead(LIGHTPIN);  // 读取 LIGHTPIN 的模拟值（光敏电阻所接引脚）
  // 将光照值映射到亮度范围
  adjustedValue = map(lightValue, LIGHT_GET_MAX, LIGHT_GET_MIN, LIGHT_CON_MAX, LIGHT_CON_MIN); 

  //设置彩灯矩阵的亮度
  matrix->setBrightness(adjustedValue);
  //将设置应用到彩灯矩阵
  matrix->show();
  return adjustedValue;
}

//显示界面的切换
void displaySwitch(displaytype disp)
{
  switch(disp)
  {
    case timedisplay:
      display = weatherdisplay;
      break;

    case weatherdisplay:
      display = roomparameter;
      is_today = 1;
      matrix->setBrightness(BRIGHTNESS);
      dir = 0;
      change = 0;
      break;

    case roomparameter:
      display = raindisplay;
      break;

    case raindisplay:
      display = music_fft;
      break;

    case music_fft:
      display = timedisplay;
      break;

      default:break;
  }
}
/*
    语音表
帧头    数据    帧尾    说明
EF      00      FE      被语音唤醒，唤醒词：小豆小豆|小豆同学|你好小豆
EF      01      FE      识别到语音：当前时间|现在时间|现在的时间|现在是什么时候了|现在多少点了|现在时间是多少|报时
EF      02      FE      识别到语音：当前温湿度|现在温湿度|现在的温湿度|温湿度是多少  
EF      03      FE      识别到语音：显示时间|切换时间界面|切换时间|显示时钟|切换时钟界面|切换时钟
EF      04      FE      识别到语音：显示天气|切换天气|切换天气界面|显示天气界面 
EF      05      FE      识别到语音：显示温湿度|切换温湿度|切换温湿度界面|显示温湿度界面
EF      06      FE      识别到语音：显示数字雨|切换数字雨|切换数字雨界面|显示数字雨界面
EF      07      FE      识别到语音：显示频谱灯|显示音律灯|切换频谱灯|切换音律灯|切换频谱灯界面|切换音频灯界面|显示频谱灯界面|显示音律灯界面
EF      08      FE      识别到语音：显示切换|切屏|界面切换|切换界面|显示下一个
 */
void voiceAnalysis(char* rx_buff)
{
  if( rx_buff[0] == 0XEF )
  {
      if( rx_buff[2] == 0xFE )
      {
          Su03t.setState(rx_buff[1]);
          vibrationMotor.vibrate(100); // 控制振动电机振动100ms
          switch (rx_buff[1])
          {
          case 0x01://当前温湿度|现在温湿度|现在的温湿度|温湿度是多少  
            readDHT(); //将温湿度参数取出并显示          
           Su03t.voiceSpeechHumiture(temperature,humidity); 
            break;
          case 0x02://当前时间|现在时间|现在的时间|现在是什么时候了|现在多少点了|现在时间是多少|报时            
            Su03t.voiceSpeechTime(hour,minute); 
            break;
          case 0x03://显示时间|切换时间界面|切换时间|显示时钟|切换时钟界面|切换时钟
            display = timedisplay;     
            break;
          case 0x04://显示天气|切换天气|切换天气界面|显示天气界面            
            display = weatherdisplay;   
            break;
          case 0x05://显示温湿度|切换温湿度|切换温湿度界面|显示温湿度界面    
            display = roomparameter;          
            break;
          case 0x06://显示数字雨|切换数字雨|切换数字雨界面|显示数字雨界面    
            display = raindisplay;          
            break;
          case 0x07://显示频谱灯|显示音律灯|切换频谱灯|切换音律灯|切换频谱灯界面|切换音频灯界面|显示频谱灯界面|显示音律灯界面  
            display = music_fft;             
            break;
          case 0x08://显示切换|切屏|界面切换|切换界面|显示下一个            
            displaySwitch(display);
            break;
           
          default:
            break;
          }
      }
  }
}


// 定义一个语音识别串口中断服务函数
void voiceSerialReceive() 
{
  // 当有数据到达时，此函数会被调用
  while (Su03t.available()) 
  {
      // 读取数据
      su03t_rx_buff[su03t_rx_len++] = Su03t.read();
      
      if( su03t_rx_len >= 3 ) 
      {
          voiceAnalysis(su03t_rx_buff);
          for(int i=0;i<10;i++)su03t_rx_buff[i] = 0;
          su03t_rx_len = 0;
      }
  }
}
